
class CollideUtils {

    //======================= 检测碰撞的 mesh ==========================
    public static raycastInfo = egret3d.RaycastInfo.create();
    public static renderer : egret3d.SkinnedMeshRenderer;
    public static raycastTriangle(pos : egret3d.Vector3, dir : egret3d.Vector3, go : paper.GameObject) : boolean {
        // 存起来 raycastInfo
        let d = dir.clone().release().normalize();
        let p = pos.clone().release().subtract(d);
        let ray = egret3d.Ray.create(p, d);
        CollideUtils.renderer = go.getComponentInChildren(egret3d.SkinnedMeshRenderer);
        if (CollideUtils.renderer && CollideUtils.renderer.raycast(ray, CollideUtils.raycastInfo, true)) {
            return true;
        }
        return false;
    }
    public static resetTriangle() {
        CollideUtils.raycastInfo = egret3d.RaycastInfo.create();
    }
    //======================= 检测碰撞 ==========================
    /**
     * 用世界空间坐标系的射线检测指定的实体。（不包含其子级）
     * @param ray 世界空间坐标系的射线。
     * @param gameObject 实体。
     * @param raycastMesh 是否检测网格。（需要消耗较多的 CPU 性能，尤其是蒙皮网格）
     * @param raycastInfo 
     */
    public static raycast(ray: Readonly<egret3d.Ray>, gameObject: Readonly<paper.GameObject>, raycastInfo?: egret3d.RaycastInfo) {
        // TODO 更快的查询所有碰撞组件的方式。extends ?
        const boxCollider = gameObject.getComponent(egret3d.BoxCollider);
        if (boxCollider && boxCollider.enabled) {
            if (raycastInfo) {
                _raycastCollider(ray, boxCollider, raycastInfo);
            }
            else if (boxCollider.raycast(ray)) {
                return true;
            }
        }

        const sphereCollider = gameObject.getComponent(egret3d.SphereCollider);
        if (sphereCollider && sphereCollider.enabled) {
            if (raycastInfo) {
                _raycastCollider(ray, sphereCollider, raycastInfo);
            }
            else if (sphereCollider.raycast(ray)) {
                return true;
            }
        }

        return (raycastInfo && raycastInfo.transform);
    }

    public static raycastAllInfos : egret3d.RaycastInfo[];
    public static raycastAllInfo = egret3d.RaycastInfo.create();
    public static addInfo() {
        if (CollideUtils.raycastAllInfo.transform) {
            CollideUtils.raycastAllInfos.push(CollideUtils.raycastAllInfo);
            CollideUtils.raycastAllInfo = egret3d.RaycastInfo.create();
        }
    }
    /**
     * 用世界空间坐标系的射线检测指定的实体或组件列表。
     * @param ray 射线。
     * @param gameObjectsOrComponents 实体或组件列表。
     * @param maxDistance 最大相交点检测距离。
     * @param cullingMask 只对特定层的实体检测。
     * @param raycastMesh 是否检测网格。（需要消耗较多的 CPU 性能，尤其是蒙皮网格）
     */
    public static raycastAll(ray: Readonly<egret3d.Ray>, gameObjects: ReadonlyArray<paper.GameObject>,
        checkChildren: boolean = false, cullingMask: paper.CullingMask = paper.Layer.Everything, needNormal: boolean = false)
    {
        CollideUtils.raycastAllInfos = [];

        for (const go of gameObjects) {
            if (needNormal)
                CollideUtils.raycastAllInfo.normal = egret3d.Vector3.create();
            _raycastAll(ray, go, checkChildren, cullingMask);
            if (CollideUtils.raycastAllInfo.transform)
            {
                CollideUtils.addInfo();
            }
            else if (CollideUtils.raycastAllInfo.normal)
            {
                CollideUtils.raycastAllInfo.normal.release();
                CollideUtils.raycastAllInfo.normal = null;
            }
        }

        CollideUtils.raycastAllInfos.sort(_sortRaycastInfo);
        return CollideUtils.raycastAllInfos;
    }
}

// ========== 碰撞相关
const _helpVector3 = egret3d.Vector3.create();
const _helpRaycastInfo = egret3d.RaycastInfo.create();
function _raycastCollider(ray: Readonly<egret3d.Ray>, collider: egret3d.BoxCollider | egret3d.SphereCollider | egret3d.CylinderCollider, raycastInfo: egret3d.RaycastInfo) {
    const helpRaycastInfo = _helpRaycastInfo;
    const normal = raycastInfo.normal;
    helpRaycastInfo.normal = normal ? _helpVector3 : null;

    if (collider.raycast(ray, helpRaycastInfo))
    {
        if (!raycastInfo.transform || raycastInfo.distance > helpRaycastInfo.distance)
        {
            const transform = collider.gameObject.transform;
            raycastInfo.distance = helpRaycastInfo.distance;
            raycastInfo.position.copy(helpRaycastInfo.position);
            raycastInfo.transform = transform;
            raycastInfo.collider = collider;

            if (normal)
                normal.copy(_helpVector3);
        }
        return true;
    }
    return false;
}

function _raycastAll(ray: Readonly<egret3d.Ray>, gameObject: Readonly<paper.GameObject>,
    checkChildren: boolean, cullingMask: paper.Layer = paper.Layer.Everything)
{
    if ((gameObject.hideFlags === paper.HideFlags.HideAndDontSave &&
        gameObject.tag == paper.DefaultTags.EditorOnly as any &&
        (!gameObject.transform.parent || gameObject.transform.parent.gameObject.activeInHierarchy)) ? gameObject.activeSelf : !gameObject.activeInHierarchy)
    {
        return false;
    }

    if (gameObject.layer & cullingMask) {
        CollideUtils.raycast(ray, gameObject, CollideUtils.raycastAllInfo);
    }

    if (checkChildren) {
        for (const child of gameObject.transform.children) {
            _raycastAll(ray, child.gameObject, checkChildren, cullingMask);
        }
    }

    return true;
}

function _sortRaycastInfo(a: egret3d.RaycastInfo, b: egret3d.RaycastInfo) {
    // TODO renderQueue.
    return a.distance - b.distance;
}